<template>
  <view :id="sliderID" class="mxp-slider" :style="cusStyle" :class="cusClass">
    <view
      class="line"
      :style="[lineStyle, { backgroundColor: props.disabled ? lightenDarkenColorStorage(props.inactiveColor, 0.7) : inactiveColor, height }]"
      @click="onClick"
    ></view>
    <view
      class="gap"
      :style="[data.barStyle, { height, backgroundColor: props.disabled ? lightenDarkenColorStorage(props.activeColor, 0.7) : activeColor }]"
      @click="onClick"
    >
      <view
        v-if="range"
        class="button-wrap"
        @touchstart="onTouchStart(1, $event)"
        @touchmove.stop.passive="onTouchMove(1, $event)"
        @touchend="onTouchEnd(1)"
        @touchcancel="onTouchEnd(1)"
      >
        <slot name="minBlock">
          <view class="button" :style="buttonStyle">
            <text v-if="showBubble" class="numsize" :style="bubbleStyle">
              {{ data.val[0] }}
            </text>
          </view>
        </slot>
      </view>
      <view
        class="button-wrap2"
        @touchstart="onTouchStart(2, $event)"
        @touchmove.stop.passive="onTouchMove(2, $event)"
        @touchend="onTouchEnd(2)"
        @touchcancel="onTouchEnd(2)"
      >
        <slot name="maxBlock">
          <view class="button" :style="buttonStyle">
            <text v-if="showBubble" class="numsize" :style="bubbleStyle">
              {{ data.val[1] }}
            </text>
          </view>
        </slot>
      </view>
    </view>
  </view>
</template>

<script lang="ts" setup>
  import { type CSSProperties, type PropType, computed, getCurrentInstance, onMounted, reactive, watch } from 'vue';
  import type { $AllColorStr, $SizeUnitStr } from '@/mxp-ui/mxp-tool/types/interface';
  import { publicStyeProps } from '../mxp-tool/utils/public-props';
  import { getDomSize } from '../mxp-tool/utils/dom';
  import { getOnlyID } from '../mxp-tool/utils/common';
  import { digitLength, times } from '../mxp-tool/utils/number';
  import { isDarkColorStorage, lightenDarkenColorStorage } from '../mxp-tool/utils/color';
  import type { TouchEvent } from 'mxp-uni-types';

  defineOptions({
    name: 'mxp-slider',
  });

  const props = defineProps({
    /** 当前进度百分比值 */
    modelValue: { type: [Array, Number] as PropType<number[] | number>, default: () => [] },
    /** 是否禁用滑块 */
    disabled: { type: Boolean, default: false },
    /** 最小值 */
    min: { type: Number, default: 0 },
    /** 最大值 */
    max: { type: Number, default: 100 },
    /** 步进值 */
    step: { type: Number, default: 1 },
    /** 是否为范围滑块 */
    range: { type: Boolean, default: false },
    /** 滑块条高度 */
    height: { type: String as PropType<$SizeUnitStr>, default: '4px' },
    /** 进度条的激活部分颜色 */
    activeColor: { type: String as PropType<$AllColorStr>, default: '#223aec' },
    /** 进度条的背景颜色 */
    inactiveColor: { type: String as PropType<$AllColorStr>, default: '#c0c4cc' },
    /** 滑块的背景颜色 */
    blockColor: { type: String as PropType<$AllColorStr>, default: '#ffffff' },
    /** 滑块的大小 */
    blockSize: { type: String as PropType<$SizeUnitStr>, default: '24px' },
    /** 滑块的阴影颜色，默认同进度条激活的颜色 */
    blockShadowColor: { type: String as PropType<$AllColorStr>, default: undefined },
    /** 是否显示气泡（显示值） */
    showBubble: { type: Boolean, default: true },
    /** 气泡的背景 */
    bubbleBackground: { type: String as PropType<$AllColorStr>, default: undefined },
    /** 气泡的文字颜色 */
    bubbleColor: { type: String as PropType<$AllColorStr>, default: undefined },
    /** 线条样式 */
    lineStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },
    ...publicStyeProps,
  });
  defineSlots<{
    /** 自定义最大划滑块 */
    maxBlock: () => void;
    /** 自定义最小划滑块 */
    minBlock: () => void;
  }>();
  const emits = defineEmits<{
    (e: 'update:modelValue', val: any): void;
    (e: 'change', val: any): void;
  }>();
  const instance = getCurrentInstance();
  const sliderID = getOnlyID('mxp-grid');
  const data = reactive({
    themeColor: props.activeColor,
    val: [] as number[],
    startX: 0,
    status: 'end',
    newValue: 0,
    distanceX: 0,
    startValue: 0,
    barStyle: {},
    sliderRect: { left: 0, width: 0 } as UniApp.NodeInfo,
  });

  watch(
    () => props.modelValue,
    (val) => {
      if (Array.isArray(val) && val.length === 2) {
        data.val[0] = props.range ? val[0]! : props.min;
        data.val[1] = val[1]!;
      } else if (typeof val === 'number') {
        data.val[0] = props.min;
        data.val[1] = val;
      } else {
        data.val[0] = props.min;
        data.val[1] = props.max;
      }
      updateValue(data.val[0]!, data.val[1]!);
    },
    { deep: true, immediate: true }
  );

  const buttonStyle = computed<CSSProperties>(() => {
    let boxShadowColor: string = props.blockShadowColor || props.activeColor;
    boxShadowColor = props.disabled ? lightenDarkenColorStorage(boxShadowColor, 0.7) || boxShadowColor : boxShadowColor;
    const blockColor = props.disabled ? lightenDarkenColorStorage(props.blockColor, 0.9) : props.blockColor;
    return {
      boxShadow: `0 0 6px ${boxShadowColor}`,
      height: props.blockSize,
      width: props.blockSize,
      backgroundColor: blockColor,
    } as unknown as CSSProperties;
  });

  const bubbleStyle = computed<CSSProperties>(() => {
    let background: string = props.bubbleBackground || props.activeColor;
    background = props.disabled ? lightenDarkenColorStorage(background, 0.7) || background : background;
    return {
      '--bubble-background': background,
      'minWidth': `${(String(Math.trunc(props.max)).length + digitLength(props.step)) / 2 + 1}em`,
      'color': props.bubbleColor || (isDarkColorStorage(background) ? '#333333' : '#ffffff'),
    } as unknown as CSSProperties;
  });

  onMounted(() => {
    // 获取滑块条的尺寸信息
    getDomSize(`#${sliderID}`, instance).then((rect: UniApp.NodeInfo) => {
      data.sliderRect = rect;
    });
  });

  /** 触摸开始 */
  function onTouchStart(type: 1 | 2, event: TouchEvent) {
    if (props.disabled) {
      return;
    }
    data.startX = 0;
    // 触摸点集
    const touches = event.touches[0]!;
    // 触摸点到屏幕左边的距离
    data.startX = touches.clientX!;
    // 此处的props.value虽为props值，但是通过$emit('input')进行了修改
    data.startValue = type === 1 ? format(data.val[0]!) : format(data.val[1]!);
    // 标示当前的状态为开始触摸滑动
    data.status = 'start';
  }

  /** 触摸移动 */
  function onTouchMove(type: 1 | 2, event: TouchEvent) {
    if (props.disabled) {
      return;
    }

    const touches = event.touches[0]!;
    // 滑块的左边不一定跟屏幕左边接壤，所以需要减去最外层父元素的左边值
    data.distanceX = touches.clientX! - data.sliderRect.left!;
    // 获得移动距离对整个滑块的百分比值，此为带有多位小数的值，不能用此更新视图
    // 否则造成通信阻塞，需要每改变一个step值时修改一次视图
    data.newValue = (data.distanceX / data.sliderRect.width!) * (props.max - props.min) + props.min;
    data.status = 'moving';
    if (type === 1) {
      updateValue(data.newValue, format(data.val[1]!));
    } else {
      updateValue(format(data.val[0]!), data.newValue);
    }
    emits('change', props.range ? data.val : data.val[1]);
  }

  /** 触摸结束 */
  function onTouchEnd(type: 1 | 2) {
    if (props.disabled) {
      return;
    }
    if (data.status === 'moving') {
      if (type === 1) {
        updateValue(data.newValue, format(data.val[1]!));
      } else {
        updateValue(format(data.val[0]!), data.newValue);
      }
    }
    data.status = 'end';
    emits('change', props.range ? data.val : data.val[1]);
  }

  /** 更新值 */
  function updateValue(value: number, value2: number) {
    // 去掉小数部分，同时也是对step步进的处理
    const widthB1 = format(value);
    const widthB2 = format(value2);
    const widthB1B = Math.round(((widthB1 - props.min) * 100) / (props.max - props.min));
    const widthB2B = Math.round(((widthB2 - props.min) * 100) / (props.max - props.min));
    // 不允许滑动的值超过max最大值，百分比也不能超过100
    if (widthB1 > widthB2 || widthB2 > props.max || widthB2B > 100) {
      return;
    }
    // 设置移动的百分比值
    const barStyle: CSSProperties = { width: `${widthB2B - widthB1B}%`, left: `${widthB1B}%` };
    if (widthB1 !== data.val[0]) {
      data.val[0] = widthB1;
    }
    if (widthB2 !== data.val[1]) {
      data.val[1] = widthB2;
    }
    // 修改value值，这里使用change改变，使用input H5桌面端会卡死，
    emits('update:modelValue', props.range ? data.val : data.val[1]!);
    data.barStyle = barStyle;
  }

  /** 格式化小数为整数 (为了减少对视图的更新，造成视图层与逻辑层的阻塞) */
  function format(value: number) {
    return times(Math.round(Math.max(props.min, Math.min(value, props.max)) / props.step), props.step);
  }

  /** 点击事件 */
  function onClick(event: TouchEvent) {
    if (props.disabled) {
      return;
    }
    const value = format(((event.detail.x - data.sliderRect.left!) / data.sliderRect.width!) * (props.max - props.min) + props.min);
    if (props.range) {
      // 直接点击滑块的情况，计算方式与onTouchMove方法相同
      const widthB1 = data.val[0]!;
      const widthB2 = data.val[1]!;
      if (value < widthB1 || value - widthB1 <= widthB2 - value) {
        // 点击位置在左滑块的左边 || 点击位置在中间，且靠近左滑块 => 移动左滑块到该位置
        updateValue(value, widthB2);
      } else {
        // 点击位置在右滑块的右边 || 点击位置在中间，且靠近右滑块 => 移动右滑块到该位置
        updateValue(widthB1, value);
      }
    } else {
      updateValue(0, value);
    }
    emits('change', props.range ? data.val : data.val[1]);
  }
</script>

<style lang="scss" scoped>
  .mxp-slider {
    position: relative;
    width: 100%;

    .line {
      position: absolute;
      width: 100%;
      background-color: #ebedf0;
      border-radius: 40px;
    }

    .gap {
      position: relative;
      background-color: #f17474;
      border-radius: 40px;
    }

    .button-wrap {
      position: absolute;
      top: 50%;
      left: 0;
      transform: translate3d(-50%, -50%, 0);
    }

    .button-wrap2 {
      position: absolute;
      top: 50%;
      right: 0;
      transform: translate3d(50%, -50%, 0);
    }

    .button {
      position: relative;
      width: 24px;
      height: 24px;
      cursor: pointer;
      background-color: #ffffff;
      border-radius: 50%;

      .numsize {
        position: absolute;
        left: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        min-width: 30px;
        padding: 4px 2px;
        font-size: 12px;
        line-height: 12px;
        color: #ffffff;
        background-color: var(--bubble-background);
        border-radius: 4px;
        transform: translate(-50%, -140%);

        &:after {
          position: absolute;
          top: 18px;
          left: 50%;
          width: 0;
          height: 0;
          content: ' ';
          border: 6px solid transparent;
          border-top-color: var(--bubble-background);
          transform: translate(-50%, 0);
        }
      }
    }
  }

  .disabled {
    opacity: 0.7;
  }
</style>
